package com.carcontrol.myplugin4.utils;

import android.annotation.TargetApi;
import android.content.Context;
import android.util.Log;

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

/**
 * 作者：created by yufenfen on 2019/3/21:12:13
 * 邮箱： ybyj1314@126.com
 */
public final class HotFix {

    public static final String PATCH_DEX_PATH = "dex";

    public static final String PATCH_DEX = "patch.dex";

    public static final String DEX_FILE_EXTENSION = ".dex";

    private static final String DEX_ELEMENTS = "dexElements";

    private static final String PATH_LIST = "pathList";

    private static final String PATH_LIST_CLASS = "dalvik.system.DexPathList";

    private static final String OPTIMIZED_PATH = "optimized";

    private static final String BASE_DEX_CLASSLOADER_CLASS = "dalvik.system.BaseDexClassLoader";

    public static void patch(Context context) {
        try {
            File patchDexDir = context.getDir(PATCH_DEX_PATH, Context.MODE_PRIVATE);
            File optimizedFile = new File(patchDexDir, OPTIMIZED_PATH);
            if (!optimizedFile.exists()) {
                optimizedFile.mkdirs();
            }
            Log.e("patch===", patchDexDir.listFiles().length+"==="+hasLexClassLoader()+"==="+hasDexClassLoader());

            for (File file : patchDexDir.listFiles()) {
                Log.e("patch===1", "==="+file.getName());

                if (file.getName().endsWith(DEX_FILE_EXTENSION)) {
//                    DexClassLoader dexClassLoader = new DexClassLoader(file.getAbsolutePath(), optimizedFile.getAbsolutePath(), null, context.getClassLoader());
//                    Object primaryElements = getDexElements(context.getClassLoader());
//                    Object dexElements = getDexElements(dexClassLoader);
//                    Object combineElements = combineArray(primaryElements, dexElements);
//                    setField(getPathList(context.getClassLoader()), DEX_ELEMENTS, combineElements);

                    PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();

                    String str = file.getAbsolutePath();
//                    String str = "/data/data/com.fengmang.scouringpad/app_dex/"+file.getName();

                    Log.e("injectAbove=0", patchDexDir.getAbsolutePath()+"==="+str);

                    Object a = combineArray(getDexElements(getPathList(pathClassLoader)),
                            getDexElements(getPathList(
                                    new DexClassLoader(str, optimizedFile.getAbsolutePath(), null, context.getClassLoader()))));

                    Log.e("injectAbove=1", patchDexDir.getAbsolutePath()+"==="+str);

                    Object a2 = getPathList(pathClassLoader);
                    setField(a2, a2.getClass(), "dexElements", a);
                }
            }
        } catch (Exception e) {
            Log.e("patch===e", "==="+e);
        }
    }


    public static void patch(Context context, String patchDexFile, String patchClassName) {
        Log.e("patch===", patchDexFile+"==="+new File(patchDexFile).exists());

        if (patchDexFile != null && new File(patchDexFile).exists()) {
            Log.e("patch===1", hasDexClassLoader()+"==="+hasLexClassLoader());
            try {
                if (hasLexClassLoader()) {
                    injectInAliyunOs(context, patchDexFile, patchClassName);
                } else if (hasDexClassLoader()) {
                    injectAboveEqualApiLevel14(context, patchDexFile, patchClassName);
                } else {

                    injectBelowApiLevel14(context, patchDexFile, patchClassName);

                }
            } catch (Exception e) {
                Log.e("patch===e", "==="+e);
            }
        }
    }

    private static boolean hasLexClassLoader() {
        try {
            Class.forName("dalvik.system.LexClassLoader");
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

    private static boolean hasDexClassLoader() {
        try {
            Class.forName("dalvik.system.BaseDexClassLoader");
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

    private static void injectInAliyunOs(Context context, String patchDexFile, String patchClassName)
            throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException,
            InstantiationException, NoSuchFieldException {
        PathClassLoader obj = (PathClassLoader) context.getClassLoader();
        String replaceAll = new File(patchDexFile).getName().replaceAll("\\.[a-zA-Z0-9]+", ".lex");
        Class cls = Class.forName("dalvik.system.LexClassLoader");
        Object newInstance =
                cls.getConstructor(new Class[] {String.class, String.class, String.class, ClassLoader.class}).newInstance(
                        new Object[] {context.getDir("dex", 0).getAbsolutePath() + File.separator + replaceAll,
                                context.getDir("dex", 0).getAbsolutePath(), patchDexFile, obj});
        cls.getMethod("loadClass", new Class[] {String.class}).invoke(newInstance, new Object[] {patchClassName});
        setField(obj, PathClassLoader.class, "mPaths",
                appendArray(getField(obj, PathClassLoader.class, "mPaths"), getField(newInstance, cls, "mRawDexPath")));
        setField(obj, PathClassLoader.class, "mFiles",
                combineArray(getField(obj, PathClassLoader.class, "mFiles"), getField(newInstance, cls, "mFiles")));
        setField(obj, PathClassLoader.class, "mZips",
                combineArray(getField(obj, PathClassLoader.class, "mZips"), getField(newInstance, cls, "mZips")));
        setField(obj, PathClassLoader.class, "mLexs",
                combineArray(getField(obj, PathClassLoader.class, "mLexs"), getField(newInstance, cls, "mDexs")));

        Log.e("injectInAliyunOs===", "==="+obj);
    }

    @TargetApi(14)
    private static void injectBelowApiLevel14(Context context, String str, String str2)
            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        PathClassLoader obj = (PathClassLoader) context.getClassLoader();
        DexClassLoader dexClassLoader =
                new DexClassLoader(str, context.getDir("dex", 0).getAbsolutePath(), str, context.getClassLoader());
        dexClassLoader.loadClass(str2);
        setField(obj, PathClassLoader.class, "mPaths",
                appendArray(getField(obj, PathClassLoader.class, "mPaths"), getField(dexClassLoader, DexClassLoader.class,
                        "mRawDexPath")
                ));
        setField(obj, PathClassLoader.class, "mFiles",
                combineArray(getField(obj, PathClassLoader.class, "mFiles"), getField(dexClassLoader, DexClassLoader.class,
                        "mFiles")
                ));
        setField(obj, PathClassLoader.class, "mZips",
                combineArray(getField(obj, PathClassLoader.class, "mZips"), getField(dexClassLoader, DexClassLoader.class,
                        "mZips")));
        setField(obj, PathClassLoader.class, "mDexs",
                combineArray(getField(obj, PathClassLoader.class, "mDexs"), getField(dexClassLoader, DexClassLoader.class,
                        "mDexs")));
        obj.loadClass(str2);

        Log.e("injectBelowApiLevel14=", "==="+obj);
    }

    private static void injectAboveEqualApiLevel14(Context context, String str, String str2)
            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException
    {
        PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();

        Log.e("injectAbove=0", context.getDir("dex", 0).getAbsolutePath()+"==="+str);

        Object a = combineArray(getDexElements(getPathList(pathClassLoader)),
                getDexElements(getPathList(
                        new DexClassLoader(str, context.getDir("dex", 0).getAbsolutePath(), str, context.getClassLoader()))));

        Log.e("injectAbove=1", context.getDir("dex", 0).getAbsolutePath()+"==="+str);

        Object a2 = getPathList(pathClassLoader);
        setField(a2, a2.getClass(), "dexElements", a);
//        pathClassLoader.loadClass(str2);

        Log.e("injectAbove=", "==="+a2);
    }

    private static Object getPathList(Object obj) throws ClassNotFoundException, NoSuchFieldException,
            IllegalAccessException {
        return getField(obj, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
    }

    private static Object getDexElements(Object obj) throws NoSuchFieldException, IllegalAccessException {
        return getField(obj, obj.getClass(), "dexElements");
    }

    private static Object getField(Object obj, Class cls, String str)
            throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = cls.getDeclaredField(str);
        declaredField.setAccessible(true);
        return declaredField.get(obj);
    }

    private static void setField(Object obj, Class cls, String str, Object obj2)
            throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = cls.getDeclaredField(str);
        declaredField.setAccessible(true);
        declaredField.set(obj, obj2);
    }

    private static Object combineArray(Object obj, Object obj2) {
        Class componentType = obj2.getClass().getComponentType();
        int length = Array.getLength(obj2);
        int length2 = Array.getLength(obj) + length;
        Object newInstance = Array.newInstance(componentType, length2);
        for (int i = 0; i < length2; i++) {
            if (i < length) {
                Array.set(newInstance, i, Array.get(obj2, i));
            } else {
                Array.set(newInstance, i, Array.get(obj, i - length));
            }
        }
        return newInstance;
    }

    private static Object appendArray(Object obj, Object obj2) {
        Class componentType = obj.getClass().getComponentType();
        int length = Array.getLength(obj);
        Object newInstance = Array.newInstance(componentType, length + 1);
        Array.set(newInstance, 0, obj2);
        for (int i = 1; i < length + 1; i++) {
            Array.set(newInstance, i, Array.get(obj, i - 1));
        }
        return newInstance;
    }















    /**
     * 修复指定的类
     *
     * @param context        上下文对象
     * @param fixDexFilePath   修复的dex文件路径
     */
    /*
    public static void fixDexFile(Context context, String fixDexFilePath) {
        Log.e("fixDexFile===", fixDexFilePath+"==="+new File(fixDexFilePath).exists());

        if (fixDexFilePath != null && new File(fixDexFilePath).exists()) {
            try {

                Log.e("fixDexFile===1", "===");
                injectDexToClassLoader(context, fixDexFilePath);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //fixDexFilePath 修复文件的路径
    private static void injectDexToClassLoader(Context context, String fixDexFilePath)
            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        //读取 baseElements
        PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
        Object basePathList = getPathList(pathClassLoader);
        Object baseElements = getDexElements(basePathList);

        //读取 fixElements
        String baseDexAbsolutePath = context.getDir("dex", 0).getAbsolutePath();

        Log.e("injectDex===1", fixDexFilePath+"==="+baseDexAbsolutePath+"==="+context.getClassLoader());

//        DexClassLoader fixDexClassLoader = new DexClassLoader(fixDexFilePath, baseDexAbsolutePath, fixDexFilePath, context.getClassLoader());
        DexClassLoader fixDexClassLoader = new DexClassLoader(fixDexFilePath, baseDexAbsolutePath, null, context.getClassLoader());
        Object fixPathList = getPathList(fixDexClassLoader);
        Object fixElements = getDexElements(fixPathList);

        //合并两份Elements
        Object newElements = combineArray(baseElements, fixElements);

        //一定要重新获取，不要用basePathList，会报错
        Object basePathList2 = getPathList(pathClassLoader);

        //新的dexElements对象重新设置回去
        setField(basePathList2, basePathList2.getClass(), "dexElements", newElements);

        Log.e("injectDex===2", basePathList2+"==="+newElements);

    }

    //通过反射先获取到pathList对象
    private static Object getPathList(Object obj) throws ClassNotFoundException, NoSuchFieldException,
            IllegalAccessException {
        return getField(obj, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
    }

    //从上面获取到的PathList对象中，进一步反射获得dexElements对象
    private static Object getDexElements(Object obj) throws NoSuchFieldException, IllegalAccessException {
        return getField(obj, obj.getClass(), "dexElements");
    }

    private static Object getField(Object obj, Class cls, String str)
            throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = cls.getDeclaredField(str);
        declaredField.setAccessible(true);//设置为可访问

        Log.e("getField===", obj+"==="+declaredField.get(obj));

        return declaredField.get(obj);
    }

    private static void setField(Object obj, Class cls, String str, Object obj2)
            throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = cls.getDeclaredField(str);
        declaredField.setAccessible(true);//设置为可访问
        declaredField.set(obj, obj2);
    }

    //合拼dexElements ,并确保 fixElements 在 baseElements 之前
    private static Object combineArray(Object baseElements, Object fixElements) {
        Class componentType = fixElements.getClass().getComponentType();
        int length = Array.getLength(fixElements);
        int length2 = Array.getLength(baseElements) + length;
        Object newInstance = Array.newInstance(componentType, length2);
        for (int i = 0; i < length2; i++) {
            if (i < length) {
                Array.set(newInstance, i, Array.get(fixElements, i));
            } else {
                Array.set(newInstance, i, Array.get(baseElements, i - length));
            }
        }
        return newInstance;
    }
    */
}